/*
 * Copyright (c) 2009 Johnny Mast
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#import "GameEngine.h"

#pragma mark -
#pragma mark Private declaration of the GameEngine

@interface GameEngine (Private) <GameEngineDelegate>
	-(void)initOpenGLES;
	-(void)updateScene:(float)delta;
	-(void)renderScene;
	-(void)layoutSubviews;
	-(BOOL)createFramebuffer;
	-(void)destroyFramebuffer;
    -(void)drawView;
@end

@implementation GameEngine
@synthesize delegate;
@synthesize lasttime;
@synthesize framerate;
@synthesize gametimer;
@synthesize backingWidth;
@synthesize backingHeight;
@synthesize viewRenderbuffer;
@synthesize depthRenderbuffer;
@synthesize viewFramebuffer;
@synthesize context;
@synthesize screenBounds;
@synthesize _XFfps;

#pragma mark -
#pragma mark Initialize or deallocate the GameEngine

// You must implement this method
+ (Class)layerClass {
    return [CAEAGLLayer class];
}

- (id)initWithFrame:(CGRect)frame {
    
    if ((self = [super initWithFrame:frame])) {
      
		delegate = self;
		
        // Get the layer
        CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
        
        eaglLayer.opaque = YES;
        eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                        [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
        
        context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
        
        if (!context || ![EAGLContext setCurrentContext:context]) {
            [self release];
            return nil;
        }
		
		
		// OpenGL init code
		
		CGRect rect = CGRectMake(0,0, 320, 480);
			
		// Setup projection mode
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glOrthof(0, rect.size.width, 0, rect.size.height, -1, 1);
		glMatrixMode(GL_MODELVIEW);
		glViewport(0, 0, rect.size.width, rect.size.height);
		
		
		
		// Initialize OpenGL states
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glDisable(GL_DEPTH_TEST); // Geen Depths in 2d games
		
		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND_SRC);
//		NSLog(@"initOpenGLES OpenGL Error: %d\n", glGetError());

		glEnableClientState(GL_VERTEX_ARRAY);
		
		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);		
		
		lasttime = CFAbsoluteTimeGetCurrent();
		_XFfps   = 0;


		[self initOpenGLES];
    }
    return self;
}



- (void)dealloc {
    if ([EAGLContext currentContext] == context) {
        [EAGLContext setCurrentContext:nil];
    }
    
    [context release];  
    [super dealloc];
}



-(void)MainGameLoop {
	
	
#if defined(GAMENGINE_DEBUG)
	NSLog(@"GameEngine: GameEngine Started (Build $Rev: 36 $)");
#endif
	
	while (TRUE) {
		
		NSAutoreleasePool* gamepool = [[NSAutoreleasePool alloc] init];
		CFTimeInterval time;
		float delta;
		
		while(CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.002, TRUE) == kCFRunLoopRunHandledSource);
		
		
		time = CFAbsoluteTimeGetCurrent();
		delta = (time - lasttime);
		
		
        // Calculate the FPS
     
		_XFfps += delta;
        if(_XFfps > 0.25f) {
            _XFfps = 0;
            XFUint _fps = (1.0f / (time - lasttime));
            // Set the FPS in the director
			
			[[Resources SharedResourceManager] setCurrentFramesPerSecond:_fps];
        }
		
		
		[self updateScene:delta];
		[self drawView];
		
		lasttime = time;
		
		[gamepool drain];
		[gamepool release];		
		
	}
}

- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event view:(UIView*)aView {
	NSLog(@"Touch in game engine");
}

- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event view:(UIView*)aView {
}

- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event view:(UIView*)aView {
}

@end

@implementation GameEngine (Private) 



-(void)updateScene:(float)delta
{
	
	[delegate updateScene:delta];
}

-(void)renderScene {
	NSLog(@"RenderScene at GameEngine");
}

-(void) initOpenGLES {
	screenBounds = CGRectMake(0,0, 320, 480);
	screenBounds = [[UIScreen mainScreen] bounds];
	
	// Switch to GL_PROJECTION matrix mode and reset the current matrix with the identity matrix
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	
	UIInterfaceOrientation DeviceOrientation = [[Resources SharedResourceManager] getCurrentDeviceOriontation];
	DeviceOrientation = UIDeviceOrientationLandscapeLeft;
	
	switch( DeviceOrientation ) {
		case UIDeviceOrientationPortrait:
			NSLog(@"GameEngine, DeviceOrientation=UIDeviceOrientationPortrait");
			glOrthof(0, screenBounds.size.width, 0, screenBounds.size.height, -1, 1);
			break;
		case UIDeviceOrientationPortraitUpsideDown:
			NSLog(@"GameEngine, DeviceOrientation=UIDeviceOrientationPortraitUpsideDown");
			glRotatef(-180,0,0,1);
			break;
		case UIDeviceOrientationLandscapeLeft:
			NSLog(@"GameEngine, DeviceOrientation=UIDeviceOrientationLandscapeLeft");
			glRotatef(-90,0,0,1);
			break;
		case UIDeviceOrientationLandscapeRight:
			NSLog(@"GameEngine, DeviceOrientation=UIDeviceOrientationLandscapeRight");
			glRotatef(90,0,0,1);
			break;
		case UIDeviceOrientationUnknown:
			NSLog(@"GameEngine, Error Unknown device orientation");
			break;
	}
	
	// Rotate the entire view 90 degrees to the left to handle the phone being in landscape mode
	if(DeviceOrientation != UIDeviceOrientationPortrait) {
		//glRotatef(-90.0f, 0, 0, 1);
		// Setup Ortho for the current matrix mode.  This describes a transformation that is applied to
		// the projection.  For our needs we are defining the fact that 1 pixel on the screen is equal to
		// one OGL unit by defining the horizontal and vertical clipping planes to be from 0 to the views
		// dimensions.  The far clipping plane is set to -1 and the near to 1.  The height and width have
		// been swapped to handle the phone being in landscape mode
		glOrthof(0, screenBounds.size.height, 0, screenBounds.size.width, -1, 1);
	} else {
		glOrthof(0, screenBounds.size.width, 0, screenBounds.size.height, -1, 1);
	}
	
	
	// Switch to GL_MODELVIEW so we can now draw our objects
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	
	// Setup how textures should be rendered i.e. how a texture with alpha should be rendered ontop of
	// another texture.
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND_SRC);
	
	// We are not using the depth buffer in our 2D game so depth testing can be disabled.  If depth
	// testing was required then a depth buffer would need to be created as well as enabling the depth
	// test
	glDisable(GL_DEPTH_TEST);
	
	// Set the colour to use when clearing the screen with glClear
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	
	
#if defined(GAMENGINE_DEBUG)
	NSLog(@"GameEngine: OpenGLES initialized");
#endif
}


- (void)drawView {
    
    // Replace the implementation of this method to do your own custom drawing
	[EAGLContext setCurrentContext:context];    
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
	
	// Clear the screen.  If we are going to draw a background image then this clear is not necessary
	// as drawing the background image will destroy the previous image
	glClear(GL_COLOR_BUFFER_BIT);
	
	// Setup how the images are to be blended when rendered.  This could be changed at different points during your
	// render process if you wanted to apply different effects
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	
	
	[delegate renderScene];
	
	// draw game scene
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER_OES];
	
}


- (void)layoutSubviews {
    [EAGLContext setCurrentContext:context];
    [self destroyFramebuffer];
    [self createFramebuffer];
	[self drawView];
}


- (BOOL)createFramebuffer {
    
    glGenFramebuffersOES(1, &viewFramebuffer);
    glGenRenderbuffersOES(1, &viewRenderbuffer);
    
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
    [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer];
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
    
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
    
    if (USE_DEPTH_BUFFER) {
        glGenRenderbuffersOES(1, &depthRenderbuffer);
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
        glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
    }
    
    if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
        NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
        return NO;
    }
    
    return YES;
}


- (void)destroyFramebuffer {
    
    glDeleteFramebuffersOES(1, &viewFramebuffer);
    viewFramebuffer = 0;
    glDeleteRenderbuffersOES(1, &viewRenderbuffer);
    viewRenderbuffer = 0;
    
    if(depthRenderbuffer) {
        glDeleteRenderbuffersOES(1, &depthRenderbuffer);
        depthRenderbuffer = 0;
    }
}
@end